// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include "coin2/exchange/base/feed/update.h"
#include "feedset/presto2/common/proto/feed.pb.h"

namespace coin2::base {

namespace impl {

using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::feed_util::Index;

inline int64_t PriceQtyToInt(double price_or_qty, int32_t precision) {
  static int32_t pow10[10] = {
      1L,
      10L,
      100L,
      1'000L,
      10'000L,
      100'000L,
      1'000'000L,
      10'000'000L,
      100'000'000L,
      1'000'000'000L
  };
  return std::llround(
      price_or_qty * pow10[precision]);
}

inline bool ConvertBookMessage(
      const IBook& book,
      int n,
      ::presto2::BookMessage* bm) {
  {
    auto* side = bm->mutable_bid();
    side->Reserve(n);
    int32_t i = 0;
    auto iter = book.BidBegin();
    auto end = book.BidEnd();
    while (i < n && iter != end) {
      auto& level = *iter;
      auto* pentry = side->Add();
      auto price = PriceQtyToInt(level.first, 8);
      auto qty = PriceQtyToInt(level.second, 3);
      pentry->set_price(price);
      pentry->set_qty(qty);
      ++i;
      ++iter;
    }
  }

  {
    auto* side = bm->mutable_ask();
    side->Reserve(n);
    int32_t i = 0;
    auto iter = book.AskBegin();
    auto end = book.AskEnd();
    while (i < n && iter != end) {
      auto& level = *iter;
      auto* pentry = side->Add();
      auto price = PriceQtyToInt(level.first, 8);
      auto qty = PriceQtyToInt(level.second, 3);
      pentry->set_price(price);
      pentry->set_qty(qty);
      ++i;
      ++iter;
    }
  }
  return true;
}

inline ::presto2::TradeSide side_convert(
    coin2::exchange::feed::TradeSide coin2_side) {
  switch (coin2_side) {
    case coin2::exchange::feed::TRADE_BUY_SIDE:
      return ::presto2::TRADE_BUY_SIDE;
    case coin2::exchange::feed::TRADE_SELL_SIDE:
      return ::presto2::TRADE_SELL_SIDE;
    case coin2::exchange::feed::TRADE_NO_SIDE:
      return ::presto2::TRADE_NO_SIDE;
    default:
      CHECK(false) << coin2::exchange::feed::TradeSide_Name(coin2_side);
      return ::presto2::TRADE_NO_SIDE;
  }
}

inline bool ConvertTradeMessage(
    const Trade& trade,
    ::presto2::TradeEntry* tm) {
  auto price = PriceQtyToInt(trade.price, 8);
  auto qty = PriceQtyToInt(trade.fill_qty, 3);
  tm->set_price(price);
  tm->set_qty(qty);
  tm->set_side(side_convert(trade.side));
  return true;
}

inline bool ConvertOpenInterestMessage(
    const OpenInterest& open_interest,
    ::presto2::OpenInterestMessage* oim) {
  auto qty = PriceQtyToInt(open_interest.open_interest_qty, 3);
  oim->set_qty(qty);
  return true;
}

inline bool ConvertIndexMessage(
    const Index& index,
    ::presto2::IndexMessage* im) {
  auto qty = PriceQtyToInt(index.price, 8);
  im->set_qty(qty);
  return true;
}


inline bool ConvertFeedMessage(
    const FeedUpdate& upd,
    int num_of_level,
    ::presto2::FeedMessage* fm) {
  fm->Clear();

  fm->set_fetched_time(upd.timestamp());
  fm->set_parse_time_cost(
      upd.ts().main_tx_timestamp() - upd.ts().main_rx_timestamp());

  if (upd.is_book()) {
    fm->set_feed_type(::presto2::BOOK_FEED);
    ConvertBookMessage(upd.book(), num_of_level, fm->mutable_book());
    fm->set_exchange_time(upd.book().Timestamp());
  } else if (upd.is_trade()) {
    fm->set_feed_type(::presto2::TRADE_FEED);
    ConvertTradeMessage(upd.trade(), fm->mutable_trade());
    fm->set_exchange_time(upd.trade().timestamp);
  } else if (upd.is_open_interest()){
    fm->set_feed_type(::presto2::OPEN_INTEREST_FEED);
    ConvertOpenInterestMessage(upd.open_interest(), fm->mutable_open_interest());
    fm->set_exchange_time(upd.open_interest().timestamp);
  } else if (upd.is_index()) {
    fm->set_feed_type(::presto2::INDEX_FEED);
    ConvertIndexMessage(upd.index(),fm->mutable_index());
    fm->set_exchange_time(upd.index().timestamp);
  }
  return true;
}

}  // namespace impl

using impl::ConvertBookMessage;
using impl::ConvertFeedMessage;
using impl::ConvertTradeMessage;
using impl::ConvertOpenInterestMessage;

}  // namespace coin2::base
